package com.lagou.edu.servlet;

import com.lagou.edu.anno.*;
import com.lagou.edu.handler.Handler;
import com.lagou.edu.handler.ModelAndView;
import com.lagou.edu.handler.Param;
import com.lagou.edu.utils.CustomUtil;
import com.lagou.edu.utils.TypeUtil;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 自定义前段控制器
 */
public class CustomDispatcherServlet extends HttpServlet {

    private final Properties properties = new Properties();
    private final List<String> classNames = new ArrayList<>();         // 缓存扫描到的类的全限定类名
    private final Map<String,Object> context = new HashMap<String,Object>();       // ioc容器
    private final List<Handler> handlerMapping = new ArrayList<>();    // 存储url和Method之间的映射关系
    private ServletContext servletContext;
    private ServletConfig config;

    /**
     * 初始化MVC
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.servletContext = config.getServletContext();
        servletContext.log("init CustomMVC start");
        try {
            // 1 加载配置⽂件
            doLoadConfig();
            // 2 扫描相关的类，扫描注解
            doScan();
            // 3 初始化bean对象（实现ioc容器，基于注解）
            doInstance();
            // 4 实现依赖注⼊
            doAutoWired();
            // 5 构造⼀个HandlerMapping处理器映射器，将配置好的url和Method建⽴映射关系
            initHandlerMapping();
        } catch (Exception e) {
            servletContext.log("init CustomMVC fail", e);
            System.exit(0);
        }
        servletContext.log("init CustomMVC end");
        super.init(config);
    }

    /**
     * 加载配置⽂件
     */
    private void doLoadConfig() throws Exception {
        this.servletContext.log("handle properties start");
        String propertyPath = this.config.getInitParameter("contextConfigLocation").replace(CustomUtil.CLASS_PATH,"");
        // 配置类
        if(propertyPath != null && propertyPath.length() == 0) {
            throw new RuntimeException("not found contextConfigLocation");
        }
        propertyPath = propertyPath.replace(CustomUtil.CLASS_PATH,"");
        InputStream inputStream = null;
        try {
            this.servletContext.log("load " + propertyPath + " start");
            inputStream = this.getClass().getClassLoader().getResourceAsStream(propertyPath);
            if (inputStream != null) {
                properties.load(inputStream);
            }  else {
                // 如果未找到配置文件,则抛出异常
                throw new RuntimeException("not found " + propertyPath);
            }
        } catch (Exception e) {
            this.servletContext.log("load " + propertyPath + " error");
            throw new RuntimeException("load "+ propertyPath + " error: " + e.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception ignored) {
                }
            }
        }
        this.servletContext.log("load " + propertyPath + " finish");
    }

    /**
     * 扫描相关的类，扫描注解
     */
    private void doScan() throws Exception {
        String scanPackage = properties.getProperty("scanPackage");
        try {
            // 包扫描路径
            this.servletContext.log("scan " + scanPackage + " start");
            String scanPackagePath = scanPackage.trim().replace(CustomUtil.POINT,CustomUtil.SLASH);
            // 项目根路径
            String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
            CustomUtil.listDirectory(new File(rootPath + scanPackagePath ), scanPackage, classNames);
        } catch (Exception e) {
            this.servletContext.log("scan " + scanPackage + " error");
            throw new RuntimeException("scan "+ scanPackage + " error: " + e.getMessage());
        }
        this.servletContext.log("scan " + scanPackage + " end");
    }

    /**
     * 初始化bean对象（实现ioc容器，基于注解）
     */
    private void doInstance() {
        this.servletContext.log("init beans start");
        try {
            for (String className : classNames) {
                // 通过反射技术实例化对象
                Class<?> aClass = Class.forName(className);
                if (!aClass.isAnnotation() && !aClass.isInterface()) {
                    if (aClass.isAnnotationPresent(CustomController.class)) {
                        // Controller
                        String beanName = CustomUtil.changeLower(aClass.getSimpleName());
                        context.put(beanName, aClass.getDeclaredConstructor().newInstance());
                    } else if (aClass.isAnnotationPresent(CustomService.class)) {
                        // Service
                        CustomService annotation = aClass.getAnnotation(CustomService.class);
                        String beanName = annotation.value();
                        if (!"".equals(beanName)) {
                            context.put(beanName, aClass.getDeclaredConstructor().newInstance());
                        } else {
                            beanName = CustomUtil.changeLower(aClass.getSimpleName());
                            context.put(beanName,aClass.getDeclaredConstructor().newInstance());
                        }
                        // service层往往是有接口的，面向接口开发，此时再以接口名为id，放⼊一份对象到context中，便于后期根据接口类型注⼊
                        Class<?>[] interfaces = aClass.getInterfaces();
                        for (Class<?> inter : interfaces) {
                            context.put(inter.getName(),aClass.getDeclaredConstructor().newInstance());
                        }
                    }
                }
            }
        } catch (Exception e) {
            this.servletContext.log("init beans error");
            throw new RuntimeException("init beans error: " + e.getMessage());
        }
        this.servletContext.log("init beans end");
    }

    /**
     * 实现依赖注⼊
     */
    private void doAutoWired() {
        this.servletContext.log("autowired beans start");
        try {
            for (String key : context.keySet()) {
                Object parentObject = context.get(key);
                Field[] fields = parentObject.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(CustomAutowired.class)) {
                        CustomAutowired annotation = field.getAnnotation(CustomAutowired.class);
                        // 需要注⼊的bean的id
                        String beanId = annotation.value();
                        if ("".equals(beanId)) {
                            // 没有配置具体的bean id，那就需要根据当前字段类型注⼊（接⼝注⼊） IDemoService
                            beanId = field.getType().getName();
                        }
                        Object instance = context.get(beanId);
                        if (instance != null) {
                            field.setAccessible(true);
                            field.set(parentObject, instance);
                        } else {
                            // 如果根据value未找到bean对象,则抛出异常
                            throw new RuntimeException("not found " + beanId + " implements " + field.getType().getSimpleName());
                        }
                        this.servletContext.log("autowired " + key + " " + beanId + " finish");
                    }
                }
            }
        } catch (Exception e) {
            this.servletContext.log("autowired beans end", e);
            throw new RuntimeException("autowired beans error : " + e.getMessage());
        }
        this.servletContext.log("autowired beans end");
    }

    /**
     * 构造⼀个HandlerMapping处理器映射器
     * 目的：将url和method建立关联
     */
    private void initHandlerMapping() {
        this.servletContext.log("init handlerMapping start");
        for (String key : context.keySet()) {
            Object parentObject = context.get(key);
            Class<?> aClass = parentObject.getClass();
            if(aClass.isAnnotationPresent(CustomController.class)){
                String baseUrl = "";
                String[] controllerSecurity = new String[]{};
                // 获取类上的 CustomRequestMapping
                if(aClass.isAnnotationPresent(CustomRequestMapping.class)) {
                    // /demo
                    baseUrl = aClass.getAnnotation(CustomRequestMapping.class).value();
                }
                // 获取类上权限校验注解
                if(aClass.isAnnotationPresent(Security.class)) {
                    // 权限信息{"tom"}
                    controllerSecurity = aClass.getAnnotation(Security.class).value();
                }
                Method[] methods = aClass.getDeclaredMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(CustomRequestMapping.class)) {
                        // /getUserNum
                        String methodUrl = method.getAnnotation(CustomRequestMapping.class).value();
                        // 计算出来的url  /demo/getUserNum
                        String url = baseUrl + methodUrl;
                        // 把method所有信息及url封装为⼀个Handler
                        Handler handler = new Handler(parentObject, method, Pattern.compile(url));
                        // handler添加类权限校验
                        if (controllerSecurity != null) {
                            handler.getSecurity().put(CustomUtil.CONTROLLER_SECURITY, Arrays.asList(controllerSecurity));
                        }
                        // handler添加方法权限校验
                        if(method.isAnnotationPresent(Security.class)) {
                            // {"tom","lucy"}
                            String[] methodSecurity = method.getAnnotation(Security.class).value();
                            if (methodSecurity != null) {
                                handler.getSecurity().put(CustomUtil.METHOD_SECURITY, Arrays.asList(methodSecurity));
                            }
                        }
                        // 计算方法的参数位置信息
                        // getUserNum(HttpServletRequestrequest, HttpServletResponse response,String name, Integer age)
                        Parameter[] parameters = method.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                            Parameter parameter = parameters[i];
                            if(parameter.getType() == HttpServletRequest.class) {
                                // 如果是request和response对象，那么参数名称写HttpServletRequest和HttpServletResponse
                                handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),new Param(i,HttpServletRequest.class.getName()));
                            } else if (parameter.getType() == HttpServletResponse.class) {
                                handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),new Param(i,HttpServletResponse.class.getName()));
                            } else {
                                // <name,{2,returnType}>
                                handler.getParamIndexMapping().put(parameter.getName(),new Param(i,parameter.getType().getName()));
                            }
                        }
                        // 建立url和method之间的映射关系（map缓存起来）
                        handlerMapping.add(handler);
                    }
                }
            }
        }
        this.servletContext.log("init handlerMapping end");
    }

    /**
     * 根据uri获取到能够处理当前请求的hanlder
     * @param url  请求地址  如:/demo/getUser
     * @return     处理器
     */
    public Handler getHandler(String url) {
        for(Handler handler: handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if(!matcher.matches()){
                continue;
            }
            return handler;
        }
        return null;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 根据url获取到能够处理当前请求的hanlder
            String url = request.getRequestURI();
        Handler handler = getHandler(url);
        if(handler == null) {
            response.getWriter().write("404 not found " + url);
            return;
        }
        try {
            // 参数绑定
            // 获取所有参数类型数组，这个数组的长度就是我们最后要传⼊的args数组的长度
            Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
            // 根据上述数组长度创建一个新的数组（参数数组，是要传入反射调用的）
            Object[] paraValues = new Object[parameterTypes.length];
            // 以下就是为了向参数数组中塞值，而且还得保证参数的顺序和方法中形参顺序一致
            Map<String, String[]> parameterMap = request.getParameterMap();
            // 权限校验
            List<String> controllerSecurity = handler.getSecurity().get(CustomUtil.CONTROLLER_SECURITY);
            List<String> methodSecurity = handler.getSecurity().get(CustomUtil.METHOD_SECURITY);
            String[] usernames = parameterMap.get(CustomUtil.USERNAME);
            if (!CustomUtil.checkSecurity(controllerSecurity, usernames)
                    || !CustomUtil.checkSecurity(methodSecurity, usernames)) {
                request.setAttribute("power", "权限不足");
                request.getRequestDispatcher("/index.jsp").forward(request, response);
                return;
            }
            // 遍历request中所有参数 （填充除了request，response之外的参数）
            for(String key: parameterMap.keySet()) {
                String[] paramValue = parameterMap.get(key);
                if (key.contains(CustomUtil.POINT)) {
                    String[] split = key.split("\\.");
                    String beanName = split[0];
                    String beanField = split[1];
                    if (handler.getParamIndexMapping().containsKey(beanName)) {
                        Param param = handler.getParamIndexMapping().get(beanName);
                        Integer index = param.getSerialNum();
                        Class<?> aClass = Class.forName(param.getType());
                        Object instance = paraValues[index];
                        if (instance == null) {
                            instance = aClass.getDeclaredConstructor().newInstance();
                        }
                        Field field = aClass.getDeclaredField(beanField);
                        field.setAccessible(true);
                        field.set(instance, TypeUtil.stringToTarget(paramValue[0], field.getType()));
                        paraValues[index] = instance;
                    }
                } else {
                    String value = paramValue[0]; // 如同 1,2
                    // 如果参数和方法中的参数匹配上了，填充数据
                    if (handler.getParamIndexMapping().containsKey(key)) {
                        // 方法形参确实有该参数，找到它的索引位置，对应的把参数值放⼊paraValues
                        Param param = handler.getParamIndexMapping().get(key);//name在第 2 个位置
                        Integer index = param.getSerialNum();
                        Class<?> aClass = Class.forName(param.getType());
                        paraValues[index] = TypeUtil.stringToTarget(value, aClass); // 把前台传递过来的参数值填充到对应的位置去
                    }
                }
            }
            Param requestParam = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
            if (requestParam != null) {
                int requestIndex = requestParam.getSerialNum();
                paraValues[requestIndex] = request;
            }
            Param responseParam = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
            if (requestParam != null) {
                int responseIndex = responseParam.getSerialNum(); // 1
                paraValues[responseIndex] = response;
            }
            // 最终调用handler的method属性
            ModelAndView modelAndView = (ModelAndView)handler.getMethod().invoke(handler.getController(), paraValues);
            // 响应值类型
            Map<String, Object> model = modelAndView.getModel();
            for (String key : model.keySet()){
                Object obj = model.get(key);
                if (CustomUtil.checkType(obj)) {
                    // 基本类型
                    request.setAttribute(key, obj);
                } else {
                    // 包装类型
                    Class<?> aClass = obj.getClass();
                    String beanId = CustomUtil.changeLower(aClass.getSimpleName());
                    Field[] fields = aClass.getDeclaredFields();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        Object value = field.get(obj);
                        String name = field.getName();
                        request.setAttribute(beanId + CustomUtil.POINT + name, value);
                    }
                }
            }
            request.getRequestDispatcher(modelAndView.getView()).forward(request, response);
        } catch (Exception e) {
            this.servletContext.log("doPost " + "url fail , cause: ", e);
            response.getWriter().write("internal error \r\n" + e.getMessage());
            return;
        }
    }

}
